package com.ilikesoup.instant.empty;

import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Optional 的 Empty 版本
 * 用 EmptyUtils#isEmpty(Object) 来替代 Optional 的非空判断
 *
 * 注意：和 Optional不同，Optional 只会判断值是否为空，所以对 value 引用的任何修改都不会影响 value 是否等于 null 的判断
 * 而 Eptional 包含的元素可能是数组、集合等，在创建 Eptional之后，对数组、集合的修改操作，会影响 EmptyUtils.isEmpty的判断
 * 所以，设计只在构造器中计算一次 EmptyUtils#isEmpty(Object) 并将结果保存在 {@link Eptional#empty} final属性中，即在创建
 * Eptional 时就已经确定它是否为 Empty了
 *
 * @see java.util.Optional
 * @see EmptyUtils#isEmpty(Object)
 * @see Emptiable
 * @param <T>
 */
public final class Eptional<T> implements Emptiable {

    private static final Eptional<?> EMPTY = new Eptional<>();

    private final T value;

    private final boolean empty;

    private Eptional() {
        this.value = null;
        this.empty = true;
    }

    public static<T> Eptional<T> empty() {
        @SuppressWarnings("unchecked")
        Eptional<T> t = (Eptional<T>) EMPTY;
        return t;
    }

    private Eptional(T value) {
        if(EmptyUtils.isEmpty(value)) {
            throw new IllegalArgumentException("value can not be empty");
        }
        this.value = value;
        this.empty = false;
    }

    public static <T> Eptional<T> of(T value) {
        return new Eptional<>(value);
    }

    public static <T> Eptional<T> ofEmptiable(T value) {
        return EmptyUtils.isEmpty(value) ? empty() : of(value);
    }

    public T get() {
        if (empty) {
            throw new IllegalStateException("Value is empty");
        }
        return value;
    }

    public void ifNotEmpty(Consumer<? super T> consumer) {
        if (!empty)
            consumer.accept(value);
    }

    public Eptional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (empty)
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

    public<U> Eptional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (empty)
            return empty();
        else {
            return Eptional.ofEmptiable(mapper.apply(value));
        }
    }

    public<U> Eptional<U> flatMap(Function<? super T, Eptional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (empty)
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

    public T orElse(T other) {
        return empty ? other : value;
    }

    public T orElseGet(Supplier<? extends T> other) {
        return empty ? other.get() : value;
    }

    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (empty) {
            throw exceptionSupplier.get();
        } else {
            return value;
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (!(obj instanceof Eptional)) {
            return false;
        }

        Eptional<?> other = (Eptional<?>) obj;
        if(other.empty != empty) {
            return false;
        }
        return Objects.equals(value, other.value);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }

    @Override
    public String toString() {
        return empty ? "Eptional.empty" : String.format("Eptional[%s]", value);
    }

    @Override
    public boolean isEmpty() {
        return empty;
    }

    private static <T> T requireNotEmpty(T value) {
        if(EmptyUtils.isEmpty(value)) {
            throw new IllegalArgumentException("value can not be empty");
        }
        return value;
    }
}
